/****************************************************************************
**
** Copyright (C) 2010 Mist Poryvaev.
** All rights reserved.
** Contact: Mist Poryvaev (mist.poryvaev@gmail.com)
**
** This file is part of the SOTA project.
**
****************************************************************************/

#include "sota.h"
#include "sota_private.h"

#include "./../var_to_xml/var_to_xml.h"
#include <QtXml>

using namespace sota_Private_Namespace;

static const QByteArray masterkey_start(QCryptographicHash::hash("START_PACKET_S0TA_IN_DA_HAUS", QCryptographicHash::Md5).toBase64());
static const QByteArray masterkey_end(QCryptographicHash::hash("END_PACKET_S0TA_IN_DA_HAUS", QCryptographicHash::Md5).toBase64());

// --------------------------------

struct SotaSender::Data
{
	QSignalSpy * spy;
	QByteArray signal, receiver, receiver_address, receiver_object, slot;
	int flags;
	QVariantMap queue;
	quint16 port;
	QObject * sender;
	QTcpSocket * socket;
	void writeQueue(); // slot for writing data when connection established
	void putToQueue(); // puts data to queue even when connection doesn't established; data will be send later
};

SotaSender::SotaSender(QObject * _sender, const char * _signal, const char * _receiver, const char * _slot, int _flags) :
	QObject(_sender), d(new Data)
{
	d->flags = _flags;
	d->spy = new QSignalSpy(d->sender = _sender, d->signal = QMetaObject::normalizedSignature(_signal));
	d->socket = new QTcpSocket();
	connect(d->socket, SIGNAL(connected()), this, SLOT(writeQueue()));
	d->port = 0;
	d->receiver = _receiver;
	if (QByteArray(_receiver).contains('/'))
	{
		QByteArray _receiver_address_and_port = QByteArray(_receiver).split('/').at(0);
		if (_receiver_address_and_port.contains(':'))
		{
			d->receiver_address = _receiver_address_and_port.split(':').at(0);
			d->port = _receiver_address_and_port.split(':').at(1).toUInt();
		}
		else
		{
			d->receiver_address = _receiver_address_and_port;
		}
		d->receiver_object = QByteArray(_receiver).split('/').at(1);
	}
	else
	{
		d->receiver_object = _receiver;
	}
	if (d->receiver_address.length() == 0)
	{
		d->receiver_address = "127.0.0.1";
	}
	if (d->port == 0)
	{
		d->port = sota::default_port;
	}
	d->slot = QMetaObject::normalizedSignature(_slot);
}

SotaSender::~SotaSender()
{
	d->socket->disconnectFromHost();
	delete d->socket;
	delete d->spy;
	delete d;
}

void SotaSender::genericSlot()
{
	d->putToQueue();
}

void SotaSender::Data::putToQueue()
{
	queue[QString::number(QTime().msecsTo(QTime::currentTime()))] = QVariantList()
			  << ((sender->objectName().length() > 0) ? QCryptographicHash::hash(sender->objectName().toAscii(), QCryptographicHash::Md5) : QByteArray())
			  << QCryptographicHash::hash(QMetaObject::normalizedSignature(signal), QCryptographicHash::Md5)
			  << ((receiver_object.length() > 0) ? QCryptographicHash::hash(receiver_object, QCryptographicHash::Md5) : QByteArray())
			  << QCryptographicHash::hash(QMetaObject::normalizedSignature(slot), QCryptographicHash::Md5)
			  << QVariant(spy->takeFirst());
	if ((socket->state() != QTcpSocket::ConnectedState) && (socket->state() != QTcpSocket::ConnectingState))
	{
		socket->connectToHost(QHostAddress(QString(receiver_address)), port);
	}
	else
	{
		writeQueue();
	}
}

void SotaSender::writeQueue()
{
	d->writeQueue();
}

void SotaSender::Data::writeQueue()
{
	foreach (const QString & _msecs_str, queue.keys())
	{
		int _delta = QTime().msecsTo(QTime::currentTime()) - _msecs_str.toInt();
		if ((quint32)(_delta < 0 ? _delta += 24 * 60 * 60 * 1000 /*midnight*/ : _delta) > sota::default_signal_ttl)
		{
			continue;
		}
		socket->write(masterkey_start);
		switch (flags & sota::Xml)
		{
		case sota::Xml:
			{
				QXmlStreamWriter _stream(socket);
				_stream.writeStartDocument();
				::var_to_xml(_stream, queue[_msecs_str].toList());
				_stream.writeEndDocument();
				break;
			}
		default:
			{
				QDataStream _stream(socket);
				_stream.setVersion(QDataStream::Qt_4_2); // 4.2 supports QVariantList
				_stream << queue[_msecs_str].toList();
				break;
			}
		}
		socket->write(masterkey_end);
	}
	queue.clear();
}

bool SotaSender::qt_connect(QObject * _sender, const char * _signal, const char * _receiver, const char * _slot, int _flags)
{
	if (_sender == 0)
	{
		return false;
	}
	SotaSender * _sota_sender = new SotaSender(_sender, _signal, _receiver, _slot, _flags);
	if (QObject::connect(_sender, _signal, _sota_sender, SLOT(genericSlot())) == false)
	{
		delete _sota_sender;
		return false;
	}
	_sender->setProperty("SotaSender_qt_connect_list",
		_sender->property("SotaSender_qt_connect_list").toList()
		<< QVariant(QByteArray::fromRawData((char *)_sota_sender, sizeof(_sota_sender)))
	);
	return true;
}

bool SotaSender::qt_disconnect(QObject * _sender, const char * _signal, const char * _receiver, const char * _slot)
{
	if (_sender == 0)
	{
		return false;
	}
	QVariantList _list = _sender->property("SotaSender_qt_connect_list").toList();
	QList<SotaSender *> _items_to_delete, _items_deleted;
	for (int _idx = _list.size() - 1; _idx >= 0; _idx--)
	{
		QByteArray _ba = _list[_idx].toByteArray();
		SotaSender * _item = (SotaSender *)_ba.constData();
		if (_item &&
			(_sender == _item->d->sender) &&
			((_signal == 0) || (QByteArray(QMetaObject::normalizedSignature(_signal)) == QByteArray(_item->d->signal))) &&
			((_receiver == 0) || (QByteArray(QMetaObject::normalizedSignature(_receiver)) == QByteArray(_item->d->receiver))) &&
			((_slot == 0) || (QByteArray(QMetaObject::normalizedSignature(_slot)) == QByteArray(_item->d->slot)))
			)
		{
			_items_to_delete << _item;
			_list.removeAt(_idx);
		}
	}
	foreach (SotaSender * _item, _items_to_delete)
	{
		if (_items_deleted.contains(_item) == false)
		{
			_items_deleted << _item;
			delete _item;
		}
	}
	_sender->setProperty("SotaSender_qt_connect_list", _list);
	return (_items_deleted.size() > 0);
}

// --------------------------------

struct SotaSocket::Data
{
	QTcpSocket * tcp_socket;
	QByteArray read_buffer;
	int new_data_size;
};

SotaSocket::SotaSocket(QTcpSocket * _tcp_socket)
	: QObject(), d(new Data)
{
	d->new_data_size = 0;
	d->tcp_socket = _tcp_socket;
	connect(d->tcp_socket, SIGNAL(disconnected()), this, SLOT(deleteLater()));
	connect(d->tcp_socket, SIGNAL(readyRead()), this, SLOT(on_socket_readyRead()));
}

SotaSocket::~SotaSocket()
{
	d->tcp_socket->deleteLater();
	delete d;
}

void SotaSocket::on_socket_readyRead()
{
	QCoreApplication::instance()->processEvents();
	if ((d->new_data_size = d->read_buffer.size()) + d->tcp_socket->bytesAvailable() > sota::default_max_arg_size)
	{
		clear();
	}
	d->read_buffer += d->tcp_socket->readAll();
	d->new_data_size = d->read_buffer.size() - d->new_data_size;
	emit readyRead(this);
}

QByteArray & SotaSocket::buffer()
{
	return d->read_buffer;
}

QHostAddress SotaSocket::peerAddress() const
{
	return d->tcp_socket->peerAddress();
}

void SotaSocket::clear(qint64 _count)
{
	if (_count == -1)
	{
		_count = d->read_buffer.size();
	}
	if (d->read_buffer.size() - _count < d->new_data_size)
	{
		d->new_data_size = d->read_buffer.size() - _count;
	}
	d->read_buffer.remove(0, _count);
}

int SotaSocket::newDataSize() const
{
	return d->new_data_size;
}


// --------------------------------

struct SotaServer::Data
{
	QTcpServer * server;
	QList<SotaReceiver *> receivers;
	static QMap<quint16, SotaServer *> pool;
};

QMap<quint16, SotaServer *> SotaServer::Data::pool;

SotaServer::SotaServer(): QObject(), d(new Data)
{
	d->server = new QTcpServer();
	connect(d->server, SIGNAL(newConnection()), this, SLOT(on_server_newConnection()));
}

SotaServer::~SotaServer()
{
	d->server->close();
	d->server->deleteLater();
	delete d;
}

void SotaServer::on_server_newConnection()
{
	while (QTcpSocket * _socket = d->server->nextPendingConnection())
	{
		SotaSocket * _sota_socket = new SotaSocket(_socket);
		connect(_sota_socket, SIGNAL(readyRead(SotaSocket *)), this, SLOT(on_socket_readyRead(SotaSocket *)), Qt::DirectConnection);
	}
}

void SotaServer::registerReceiver(SotaReceiver * _sota_receiver, quint16 _port)
{
	if (_sota_receiver == 0)
	{
		return;
	}
	if (Data::pool.contains(_port) == false)
	{
		Data::pool[_port] = new SotaServer();
		Data::pool[_port]->d->server->listen(QHostAddress::Any, _port);
	}
	if (Data::pool[_port]->d->receivers.contains(_sota_receiver))
	{
		return;
	}
	Data::pool[_port]->d->receivers << _sota_receiver;
}

void SotaServer::unregisterReceiver(SotaReceiver * _sota_receiver, quint16 _port)
{
	if (_sota_receiver == 0)
	{
		return;
	}
	if (!Data::pool.contains(_port))
	{
		return;
	}
	Data::pool[_port]->d->receivers.removeOne(_sota_receiver);
	if (Data::pool[_port]->d->receivers.count() == 0)
	{
		Data::pool[_port]->d->server->close();
		delete Data::pool[_port];
		Data::pool.remove(_port);
	}
}

bool SotaServer::isListening(quint16 _port)
{
	return Data::pool.contains(_port) ? Data::pool[_port]->d->server->isListening() : false;
}

void SotaServer::on_socket_readyRead(SotaSocket * _sota_socket)
{
	QByteArray & _data = _sota_socket->buffer();
	const QByteArray _peerAddress(_sota_socket->peerAddress().toString().toAscii());

	bool _start_found, _end_found;
	int _start, _end;
	const int _start_length = masterkey_start.length(), _end_length = masterkey_end.length();

	do
	{
		const int _new_data_offset = _data.size() - _sota_socket->newDataSize();
		const QByteArray & _new_data = QByteArray::fromRawData(_data.constData() + _new_data_offset, _sota_socket->newDataSize());

		_start_found = (_start = _new_data.indexOf(masterkey_start)) >= 0;
		_end_found = (_end = _new_data.indexOf(masterkey_end)) >= 0;
		if (_end_found)
		{
			if (_sota_socket->property("start_found").toBool())
			{
				const QByteArray & _packet_data = QByteArray::fromRawData(_data.constData(), _end + _new_data_offset);
				foreach (SotaReceiver * _receiver, d->receivers)
				{
					_receiver->invoke(_packet_data, _peerAddress);
				}
				_sota_socket->setProperty("start_found", _start_found = false);
			}
			if (!_start_found)
			{
				_sota_socket->clear(_end + _end_length + _new_data_offset);
			}
		}

		if (_start_found)
		{
			_sota_socket->clear(_start + _start_length + _new_data_offset);
			_sota_socket->setProperty("start_found", true);
		}
	} while ((_start_found || _end_found) && (_sota_socket->newDataSize() > 0));
}


// --------------------------------

struct SotaReceiver::Data
{
	QByteArray signal, sender, sender_address, sender_object, slot;
	quint16 port;
	QObject * receiver;
	static QMap<QThread *, QHostAddress> caller_address;
	static QMutex caller_address_mutex;
	void invoke(const QByteArray & _data, const QByteArray & _peerAddress); // invokes method of connected object
};

QMap<QThread *, QHostAddress> SotaReceiver::Data::caller_address;
QMutex SotaReceiver::Data::caller_address_mutex;

SotaReceiver::SotaReceiver(const char * _sender, const char * _signal, QObject * _receiver, const char * _slot)
	: QObject(_receiver), d(new Data)
{
	d->receiver = _receiver;
	if (d->receiver == 0)
	{
		return;
	}

	d->slot = QMetaObject::normalizedSignature(_slot);
	QByteArray _slot_no_prefix = d->slot;
	while ((_slot_no_prefix[0] >= '0') && (_slot_no_prefix[0] <= '9'))
	{
		_slot_no_prefix.remove(0, 1);
	}
	if (d->receiver->metaObject()->indexOfSlot(_slot_no_prefix) == -1)
	{
		return;
	}

	d->signal = QMetaObject::normalizedSignature(_signal);
	d->port = sota::default_port;
	d->sender = _sender;
	if (QByteArray(_sender).contains('/'))
	{
		QByteArray _sender_address_and_port = QByteArray(_sender).split('/').at(0);
		if (_sender_address_and_port.contains(':'))
		{
			d->sender_address = _sender_address_and_port.split(':').at(0);
			d->port = _sender_address_and_port.split(':').at(1).toUInt();
		}
		d->sender_object = QByteArray(_sender).split('/').at(1);
	}
	else
	{
		d->sender_object = _sender;
	}

	SotaServer::registerReceiver(this, d->port);
}

SotaReceiver::~SotaReceiver()
{
	SotaServer::unregisterReceiver(this, d->port);
	delete d;
}

bool SotaReceiver::isServerListening() const
{
	return SotaServer::isListening(d->port);
}

void SotaReceiver::invoke(const QByteArray &_data, const QByteArray &_peerAddress)
{
	d->invoke(_data, _peerAddress);
}

void SotaReceiver::Data::invoke(const QByteArray & _data, const QByteArray & _peerAddress)
{
	QVariantList _list;
	QDataStream _stream(_data);
	_stream >> _list;
	if (_stream.status() != QDataStream::Ok)
	{
		QVariant _value;
		QXmlStreamReader _reader(_data);
		QString _name;
		::xml_to_var(_reader, _value, _name);
		if (_reader.error() != QXmlStreamReader::NoError)
		{
			return;
		}
		_list = _value.toList();
	}

	if (_list.size() != 5)
	{
		return;
	}

	QByteArray _sender_object = _list.takeFirst().toByteArray();
	QByteArray _signal = _list.takeFirst().toByteArray();
	QByteArray _receiver_object = _list.takeFirst().toByteArray();
	QByteArray _slot = _list.takeFirst().toByteArray();
	QVariantList _arguments;
	if (_list.size() > 0)
	{
		_arguments = _list.takeFirst().toList();
	}
	if (
		(receiver == 0)
		||
		((_receiver_object.length() > 0) && (QString(_receiver_object).compare(QCryptographicHash::hash(receiver->objectName().toAscii(), QCryptographicHash::Md5)) != 0))
		||
		((sender_object.length() > 0) && (QCryptographicHash::hash(sender_object, QCryptographicHash::Md5) != _sender_object))
		||
		(QCryptographicHash::hash(signal, QCryptographicHash::Md5) != _signal)
		||
		(QCryptographicHash::hash(slot, QCryptographicHash::Md5) != _slot)
		||
		((sender_address.length() > 0) && (sender_address != _peerAddress))
		)
	{
		return;
	}

	QByteArray _slot_no_prefix = slot;
	while ((_slot_no_prefix[0] >= '0') && (_slot_no_prefix[0] <= '9'))
	{
		_slot_no_prefix.remove(0, 1);
	}
	_slot = _slot_no_prefix.split('(').at(0);

	int _arguments_count = _slot_no_prefix.split(',').count();
	while (_arguments_count < _arguments.size())
	{
		_arguments.removeLast();
	}

	/* caller_address() function support */
	caller_address_mutex.lock();
	caller_address[QThread::currentThread()] = QHostAddress(QString(_peerAddress));
	caller_address_mutex.unlock();

	switch (_arguments.size())
	{
	case 0:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection
				);
		break;
	case 1:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData())
				);
		break;
	case 2:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData())
				);
		break;
	case 3:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData())
				);
		break;
	case 4:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData())
				);
		break;
	case 5:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData()),
				QGenericArgument(_arguments[4].typeName(), (const void *)_arguments[4].constData())
				);
		break;
	case 6:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData()),
				QGenericArgument(_arguments[4].typeName(), (const void *)_arguments[4].constData()),
				QGenericArgument(_arguments[5].typeName(), (const void *)_arguments[5].constData())
				);
		break;
	case 7:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData()),
				QGenericArgument(_arguments[4].typeName(), (const void *)_arguments[4].constData()),
				QGenericArgument(_arguments[5].typeName(), (const void *)_arguments[5].constData()),
				QGenericArgument(_arguments[6].typeName(), (const void *)_arguments[6].constData())
				);
		break;
	case 8:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData()),
				QGenericArgument(_arguments[4].typeName(), (const void *)_arguments[4].constData()),
				QGenericArgument(_arguments[5].typeName(), (const void *)_arguments[5].constData()),
				QGenericArgument(_arguments[6].typeName(), (const void *)_arguments[6].constData()),
				QGenericArgument(_arguments[7].typeName(), (const void *)_arguments[7].constData())
				);
		break;
	case 9:
		QMetaObject::invokeMethod(
				receiver,
				_slot,
				Qt::DirectConnection,
				QGenericArgument(_arguments[0].typeName(), (const void *)_arguments[0].constData()),
				QGenericArgument(_arguments[1].typeName(), (const void *)_arguments[1].constData()),
				QGenericArgument(_arguments[2].typeName(), (const void *)_arguments[2].constData()),
				QGenericArgument(_arguments[3].typeName(), (const void *)_arguments[3].constData()),
				QGenericArgument(_arguments[4].typeName(), (const void *)_arguments[4].constData()),
				QGenericArgument(_arguments[5].typeName(), (const void *)_arguments[5].constData()),
				QGenericArgument(_arguments[6].typeName(), (const void *)_arguments[6].constData()),
				QGenericArgument(_arguments[7].typeName(), (const void *)_arguments[7].constData()),
				QGenericArgument(_arguments[8].typeName(), (const void *)_arguments[8].constData())
				);
		break;
	}

	/* caller_address() function support */
	caller_address_mutex.lock();
	caller_address.remove(QThread::currentThread());
	caller_address_mutex.unlock();
}

QHostAddress SotaReceiver::caller_address()
{
	QMutexLocker _locker(&Data::caller_address_mutex);
	return (
			Data::caller_address.contains(QThread::currentThread())
			? Data::caller_address[QThread::currentThread()]
				: QHostAddress()
			);
}

bool SotaReceiver::qt_connect(const char * _sender, const char * _signal, QObject * _receiver, const char * _slot)
{
	if (_receiver == 0)
	{
		return false;
	}
	SotaReceiver * _sota_receiver = new SotaReceiver(_sender, _signal, _receiver, _slot);
	if (_sota_receiver->isServerListening() == false)
	{
		delete _sota_receiver;
		return false;
	}
	_receiver->setProperty("SotaReceiver_qt_connect_list",
		_receiver->property("SotaReceiver_qt_connect_list").toList()
		<< QVariant(QByteArray::fromRawData((char *)_sota_receiver, sizeof(_sota_receiver)))
	);
	return true;
}

bool SotaReceiver::qt_disconnect(const char * _sender, const char * _signal, QObject * _receiver, const char * _slot)
{
	if (_receiver == 0)
	{
		return false;
	}
	QVariantList _list = _receiver->property("SotaReceiver_qt_connect_list").toList();
	QList<SotaReceiver *> _items_to_delete, _items_deleted;
	for (int _idx = _list.size() - 1; _idx >= 0; _idx--)
	{
		QByteArray _ba = _list[_idx].toByteArray();
		SotaReceiver * _item = (SotaReceiver *)_ba.constData();
		if (_item &&
			((_sender == 0) || (QByteArray(QMetaObject::normalizedSignature(_sender)) == QByteArray(_item->d->sender))) &&
			((_signal == 0) || (QByteArray(QMetaObject::normalizedSignature(_signal)) == QByteArray(_item->d->signal))) &&
			(_receiver == _item->d->receiver) &&
			((_slot == 0) || (QByteArray(QMetaObject::normalizedSignature(_slot)) == QByteArray(_item->d->slot)))
			)
		{
			_items_to_delete << _item;
			_list.removeAt(_idx);
		}
	}
	foreach (SotaReceiver * _item, _items_to_delete)
	{
		if (_items_deleted.contains(_item) == false)
		{
			_items_deleted << _item;
			delete _item;
		}
	}
	_receiver->setProperty("SotaReceiver_qt_connect_list", _list);
	return (_items_deleted.size() > 0);
}

// --------------------------------

SOTA_DLL quint16 sota::default_port = 52871;
SOTA_DLL quint32 sota::default_signal_ttl = 30000; // 30 sec
SOTA_DLL quint32 sota::default_max_arg_size = 268435456; // 256 Mb

SOTA_DLL bool sota::connect(QObject * _sender, const char * _signal, const char * _receiver, const char * _slot, int _flags)
{
	return SotaSender::qt_connect(_sender, _signal, _receiver, _slot, _flags);
}

SOTA_DLL bool sota::disconnect(QObject *_sender, const char *_signal, const char *_receiver, const char *_slot)
{
	return SotaSender::qt_disconnect(_sender, _signal, _receiver, _slot);
}

SOTA_DLL bool sota::connect(const char * _sender, const char * _signal, QObject * _receiver, const char * _slot)
{
	return SotaReceiver::qt_connect(_sender, _signal, _receiver, _slot);
}

SOTA_DLL bool sota::disconnect(const char * _sender, const char * _signal, QObject * _receiver, const char * _slot)
{
	return SotaReceiver::qt_disconnect(_sender, _signal, _receiver, _slot);
}

SOTA_DLL QString sota::caller_address()
{
	return SotaReceiver::caller_address().toString();
}

// --------------------------------

